#include "Layer.h"

#include <vector>

#include "Exception\LayerException.h"

namespace ANNT
{
	namespace Layers
	{

		CLayer::CLayer(const initializer_list<int> & inputMatrixDimation, const initializer_list<int> & outputMatrixDimation) :
			m_NextLayer(nullptr),
			m_PrevLayer(nullptr),
			m_InputMatrix(nullptr),
			m_OutputMatrix(nullptr),
			m_FrontwardLocalGradient(nullptr),
			m_MyLocalGradient(nullptr)
		{
			vector<int> inputDimation;
			vector<int> outputDimation;

			for (auto v = inputMatrixDimation.begin(); v != inputMatrixDimation.end(); v++)
			{
				inputDimation.push_back(*v);
			}

			for (auto v = outputMatrixDimation.begin(); v != outputMatrixDimation.end(); v++)
			{
				outputDimation.push_back(*v);
			}

			if (inputDimation.size() != 2 || outputDimation.size() != 2)
			{
				THROW_LAYER_DIMATION_EXCEPTION;
			}

			this->m_InputMatrix = make_shared<Matrix2>(inputDimation[1], inputDimation[0]);

			this->m_OutputMatrix = make_shared<Matrix2>(outputDimation[1], outputDimation[0]);

			this->CreateLocalGradientMatrix();
		}

		CLayer::~CLayer()
		{
			this->m_NextLayer = nullptr;
			this->m_PrevLayer = nullptr;

			this->m_InputMatrix = nullptr;
			this->m_OutputMatrix = nullptr;
		}

		int CLayer::GetInputMatrixLineCount()
		{
			return this->m_InputMatrix->GetLineCount();
		}

		int CLayer::GetInputMatrixColumnCount()
		{
			return this->m_InputMatrix->GetColumnCount();
		}

		int CLayer::GetOutputMatrixLineCount()
		{
			return this->m_OutputMatrix->GetLineCount();
		}

		int CLayer::GetOutputMatrixColumnCount()
		{
			return this->m_OutputMatrix->GetColumnCount();
		}

		void CLayer::SetInputMatrix(const Matrix2 & inputMatrix)
		{
			if (inputMatrix.GetLineCount() != this->m_InputMatrix->GetLineCount() ||
				inputMatrix.GetColumnCount() != this->m_InputMatrix->GetColumnCount())
			{
				THROW_LAYER_DIMATION_EXCEPTION;
			}

			*this->m_InputMatrix.get() = inputMatrix;
		}

		const Matrix2 & CLayer::GetOutputMatrix()
		{
			return *this->m_OutputMatrix.get();
		}

		void CLayer::SetFrontwardLayerLocalGradient(const Matrix2 & backwardLayerLocalGradient)
		{
			if (backwardLayerLocalGradient.GetLineCount() != this->m_FrontwardLocalGradient->GetLineCount() ||
				backwardLayerLocalGradient.GetColumnCount() != this->m_FrontwardLocalGradient->GetColumnCount())
			{
				THROW_LAYER_DIMATION_EXCEPTION;
			}

			*this->m_FrontwardLocalGradient.get() = backwardLayerLocalGradient;
		}

		const Matrix2 & CLayer::GetBackwardLayerLocalGradient()
		{
			return *this->m_MyLocalGradient.get();
		}

		bool CLayer::ConnectLayer(std::shared_ptr<CLayer> & nextLayer)
		{
			bool bCanConnect = nextLayer->OnLayerConnected(this);
			if (!bCanConnect)
			{
				return false;
			}

			this->m_NextLayer = nextLayer;
			nextLayer->m_PrevLayer = this;

			return true;
		}

		CLayer * CLayer::GetNextLayer()
		{
			return this->m_NextLayer.get();
		}

		CLayer * CLayer::GetPrevLayer()
		{
			return this->m_PrevLayer;
		}

		const Matrix2 & CLayer::InspireLayerChain(const Matrix2 & inspireData)
		{
			if (this->GetPrevLayer() != nullptr)
			{
				THROW_LAYER_DIMATION_EXCEPTION
			}

			CLayer * curLayer = this;
			curLayer->SetInputMatrix(inspireData);

			while (true)
			{
				curLayer->Execute();

				CLayer * nextLayer = curLayer->GetNextLayer();
				if (nextLayer == nullptr)
				{
					return curLayer->GetOutputMatrix();
				}
				else
				{
					nextLayer->SetInputMatrix(curLayer->GetOutputMatrix());
					curLayer = nextLayer;
				}
			}
		}

		void CLayer::CorrectingLayerChain(const Matrix2 & errorData, std::function<double()> & learnRateFunction)
		{
			if (this->GetNextLayer() != nullptr)
			{
				THROW_LAYER_DIMATION_EXCEPTION
			}

			CLayer * curLayer = this;
			curLayer->SetFrontwardLayerLocalGradient(errorData);

			while (true)
			{
				curLayer->Training(learnRateFunction);

				CLayer * prevLayer = curLayer->GetPrevLayer();
				if (prevLayer == nullptr)
				{
					return;
				}
				else
				{
					prevLayer->SetFrontwardLayerLocalGradient(curLayer->GetBackwardLayerLocalGradient());
					curLayer = prevLayer;
				}
			}
		}
	}
};